学习自https://blog.csdn.net/txl199106/article/details/64441994
一.网络流:流&网络&割
1.网络流问题(NetWork Flow Problem):
给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow).
下面给出一个通俗点的解释
(下文基本避开形式化的证明 基本都用此类描述叙述)
好比你家是汇 自来水厂(有需要的同学可以把自来水厂当成银行之类 以下类似)是源
然后自来水厂和你家之间修了很多条水管子接在一起 水管子规格不一 有的容量大 有的容量小
然后问自来水厂开闸放水 你家收到水的最大流量是多少
如果自来水厂停水了 你家那的流量就是0 当然不是最大的流量
但是你给自来水厂交了100w美金 自来水厂拼命水管里通水 但是你家的流量也就那么多不变了 这时就达到了最大流
2.三个基本的性质:
如果 C代表每条边的容量 F代表每条边的流量
一个显然的实事是F小于等于C 不然水管子就爆了,这就是网络流的第一条性质 容量限制(Capacity Constraints):F
再考虑节点任意一个节点 流入量总是等于流出的量 否则就会蓄水(爆炸危险…)或者平白无故多出水(有地下水涌出?)
这是第二条性质 流量守恒(Flow Conservation):Σ F
当然源和汇不用满足流量守恒 我们不用去关心自来水厂的水是河里的 还是江里的
最后一个不是很显然的性质 是斜对称性(Skew Symmetry): F
这其实是完善的网络流理论不可缺少的 就好比中学物理里用正负数来定义一维的位移一样
百米起点到百米终点的位移是100m的话 那么终点到起点的位移就是-100m
同样的 x向y流了F的流 y就向x流了-F的流
对于任意一个时刻,设f(u,v)实际流量,则整个图G的流网络满足3个性质:
容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。
反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。从u到v的流量一定是从v到u的流量的相反值。
流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会”制造”和”消耗”流量。
3.容量网络&流量网络&残留网络:
网络就是有源汇的有向图 关于什么就是指边权的含义是什么
容量网络就是关于容量的网络 基本是不改变的(极少数问题需要变动)
流量网络就是关于流量的网络 在求解问题的过程中
通常在不断的改变 但是总是满足上述三个性质
调整到最后就是最大流网络 同时也可以得到最大流值
残留网络往往概括了容量网络和流量网络 是最为常用的
残留网络=容量网络-流量网络
这个等式是始终成立的 残留值当流量值为负时甚至会大于容量值
流量值为什么会为负?有正必有负,记住斜对称性!
4.割&割集:
无向图的割集(Cut Set):C[A,B]是将图G分为A和B两个点集 A和B之间的边的全集
网络的割集:C[S,T]是将网络G分为s和t两部分点集 S属于s且T属于t 从S到T的边的全集
带权图的割(Cut)就是割集中边或者有向边的权和
通俗的理解一下:
割集好比是一个恐怖分子 把你家和自来水厂之间的水管网络砍断了一些
然后自来水厂无论怎么放水 水都只能从水管断口哗哗流走了 你家就停水了
割的大小应该是恐怖分子应该关心的事 毕竟细管子好割一些
而最小割花的力气最小
二.计算最大流的基本算法
那么怎么求出一个网络的最大流呢?
这里介绍一个最简单的算法:
Edmonds-Karp算法 即最短路径增广算法 简称EK算法
EK算法基于一个基本的方法:Ford-Fulkerson方法 即增广路方法 简称FF方法
增广路方法是很多网络流算法的基础 一般都在残留网络中实现
其思路是每次找出一条从源到汇的能够增加流的路径 调整流值和残留网络 不断调整直到没有增广路为止
FF方法的基础是增广路定理(Augmenting Path Theorem):网络达到最大流当且仅当残留网络中没有增广路
证明略 这个定理应该能够接受的吧
EK算法就是不断的找最短路 找的方法就是每次找一条边数最少的增广 也就是最短路径增广
这样就产生了三个问题:
1.最多要增广多少次?
可以证明 最多O(VE)次增广 可以达到最大流 证明略
2.如何找到一条增广路?
先明确什么是增广路 增广路是这样一条从s到t的路径 路径上每条边残留容量都为正
把残留容量为正的边设为可行的边 那么我们就可以用简单的BFS得到边数最少的增广路
.如何增广?
BFS得到增广路之后 这条增广路能够增广的流值 是路径上最小残留容量边决定的
把这个最小残留容量MinCap值加到最大流值Flow上 同时路径上每条边的残留容量值减去MinCap
最后 路径上每条边的反向边残留容量值要加上MinCap 为什么? 下面会具体解释
这样每次增广的复杂度为O(E) EK算法的总复杂度就是O(VE^2),事实上 大多数网络的增广次数很少 EK算法能处理绝大多数问题,平均意义下增广路算法都是很快的
增广路算法好比是自来水公司不断的往水管网里一条一条的通水
上面还遗留了一个反向边的问题: 为什么增广路径上每条边的反向边残留容量值要加上MinCap?
因为斜对称性! 由于残留网络=容量网络-流量网络
容量网络不改变的情况下
由于增广好比给增广路上通了一条流 路径说所有边流量加MinCap
流量网络中路径上边的流量加MinCap 反向边流量减去MinCap
相对应的残留网络就发生相反的改变
这样我们就完成了EK算法 具体实现可以用邻接表存图 也可以用邻接矩阵存图
邻接表存图 由于流量同时存在于边与反向边 为了方便求取反向边 建图把一对互为反向边的边建在一起
代码很简单 最好自己实现一下
看一个具体的增广路算法的例子吧
=====================================================================
三.最大流最小割定理
下面介绍网络流理论中一个最为重要的定理
最大流最小割定理(Maximum Flow, Minimum Cut Theorem):网络的最大流等于最小割
具体的证明分三部分
1.任意一个流都小于等于任意一个割
这个很好理解 自来水公司随便给你家通点水 构成一个流
恐怖分子随便砍几刀 砍出一个割
由于容量限制 每一根的被砍的水管子流出的水流量都小于管子的容量
每一根被砍的水管的水本来都要到你家的 现在流到外面 加起来得到的流量还是等于原来的流
管子的容量加起来就是割 所以流小于等于割
由于上面的流和割都是任意构造的 所以任意一个流小于任意一个割
2.构造出一个流等于一个割
当达到最大流时 根据增广路定理
残留网络中s到t已经没有通路了 否则还能继续增广
我们把s能到的的点集设为S 不能到的点集为T
构造出一个割集C[S,T] S到T的边必然满流 否则就能继续增广
这些满流边的流量和就是当前的流即最大流
把这些满流边作为割 就构造出了一个和最大流相等的割
3.最大流等于最小割
设相等的流和割分别为Fm和Cm
则因为任意一个流小于等于任意一个割
任意F≤Fm=Cm≤任意C
定理说明完成,证明如下:
1 | 对于一个网络流图G=(V,E),其中有源点s和汇点t,那么下面三个条件是等价的: |
首先证明1 => 2:1
我们利用反证法,假设流f是图G的最大流,但是残留网络中还存在有增广路p,其流量为fp。则我们有流f'=f+fp>f。这与f是最大流产生矛盾。
接着证明2 => 3:1
2
3假设残留网络Gf不存在增广路,所以在残留网络Gf中不存在路径从s到达t。我们定义S集合为:当前残留网络中s能够到达的点。同时定义T=V-S。
此时(S,T)构成一个割(S,T)。且对于任意的u∈S,v∈T,有f(u,v)=c(u,v)。若f(u,v)<c(u,v),则有Gf(u,v)>0,s可以到达v,与v属于T矛盾。
因此有f(S,T)=Σf(u,v)=Σc(u,v)=C(S,T)。
最后证明3 => 1:1
2由于f的上界为最小割,当f到达割的容量时,显然就已经到达最大值,因此f为最大流。
这样就说明了为什么找不到增广路时,所求得的一定是最大流。
最大流
最大流相关算法有两种解决思想, 一种是增广路算法思想, 另一种是预流推进算法思想。 下面将分别介绍这两种算法思想。
增广路算法(Ford-Fulkerson)
基本思想
根据增广路定理, 为了得到最大流, 可以从任何一个可行流开始, 沿着增广路对网络流进行增广, 直到网络中不存在增广路为止,这样的算法称为增广路算法。问题的关键在于如何有效地找到增广路, 并保证算法在有限次增广后一定终止。
增广路算法的基本流程是 :
(1) 取一个可行流 f 作为初始流(如果没有给定初始流,则取零流 f= { 0 }作为初始流);
(2) 寻找关于 f 的增广路 P,如果找到,则沿着这条增广路 P 将 f 改进成一个更大的流, 并建立相应的反向弧;
(3) 重复第(2)步直到 f 不存在增广路为止。
图示如下:
增广路算法的关键是 寻找增广路 和 改进网络流。
问题: 为什么要创建反向弧呢?
原因: 为程序提供一次反悔的机会 什么意思, 如下图所示:
在图中如果程序找到了一条增广路 1 -> 2 -> 4 -> 6, 此时得到一个流量为 2 的流并且无法继续进行增广,
但是如果在更新可行流的同时建立反向弧的话, 就可以找到 1 -> 3 -> 4 -> 2 -> 5 -> 6 的可行流, 流量为1, 这样就可以得到最大流为 3.
一般增广路算法(EdmondsKarp)
算法流程
在一般的增广路算法中, 程序的实现过程与增广路求最大流的过程基本一致. 即每一次更新都进行一次找增广路然后更新路径上的流量的过程。但是我们可以从上图中发现一个问题, 就是每次找到的增广路曲曲折折非常长, 此时我们往往走了冤枉路(即:明明我们可以从源点离汇点越走越进的,可是中间的几条边却向离汇点远的方向走了), 此时更新增广路的复杂度就会增加。EK 算法为了规避这个问题使用了 bfs 来寻找增广路, 然后在寻找增广路的时候总是向离汇点越来越近的方向去寻找下一个结点。
算法实现
邻接矩阵
1 |
|
邻接表
1 | const int MAXN = 430; |
算法复杂度
每进行一次增广需要的时间复杂度为 bfs 的复杂度 + 更新残余网络的复杂度, 大约为 O(m)(m为图中的边的数目), 需要进行多少次增广呢, 假设每次增广只增加1, 则需要增广 nW 次(n为图中顶点的数目, W为图中边上的最大容量), .
Dinic算法
算法思想
DINIC 在找增广路的时候也是找的最短增广路, 与 EK 算法不同的是 DINIC 算法并不是每次 bfs 只找一个增广路, 他会首先通过一次 bfs 为所有点添加一个标号, 构成一个 层次图, 然后在层次图中寻找增广路进行更新。
算法流程
1 | 1.利用 BFS 对原来的图进行分层,即对每个结点进行标号, 这个标号的含义是当前结点距离源点的最短距离(假设每条边的距离都为1),注意:构建层次图的时候所走的边的残余流量必须大于0 |
算法实现
1 |
|
当前弧优化和多路增广
1 |
|
时间复杂度
$O(V^2E)
最短增广路算法(SAP)
算法思想
最短增广路算法是一种运用距离标号使寻找增广路的时间复杂度下降的算法。所谓的距离标号就是某个点到汇点的最少的弧的数量(即当边权为1时某个点的最短路径长度). 设点i的标号为d[i], 那么如果将满足d[i] = d[j] + 1, 且增广时只走允许弧, 那么就可以达到”怎么走都是最短路”的效果. 每个点的初始标号可以在一开始用一次从汇点沿所有反向的BFS求出.
算法流程
1 | 1) 定义节点的标号为到汇点的最短距离; |
需要注意的是, 标号的更新过程首先我们要理解更新标号的目的。标号如果需要更新,说明在当前的标号下已经没有增广路可以继续走,这时更新标号就可以使得我们有继续向下走的可能,并且每次找的都是能走到的点中标号最小的那个点,这样也使得每次搜索长度最小.
下面的图演示了标号的更新过程:
1.首先我们假设有个图如下,为了简化没有标箭头也没有写流量:
2.为图标号, 每个点的标号为其到汇点的最短距离(这里把每条边看作1)
3.第一遍遍历时,找到了1->2->9这样一条增广路以后,更新边上流量值, 得到下图
棕色字体为边上的流量值。这时按照标号再搜一遍,发现从1出发已经找不到增广路了,因为flow(1,2)等于0不可以走,h[1]=2,h[3]=2≠h[1]+1,h[5]=4≠h[1]+1, 所以这时更新1的标号,
4.按照 min(h[next]|Flow(now,next)>0)+1,修改后 h[1]=h[3]+1=3.
5.第二遍遍历以后找到了这样一条增广路:1->3->4->9,做完这条路以后又发现无法找到可行边了,这时再更新标号使图中有路可走,如上文所说的那样做,再次修改后h[1]=h[5]+1=5h[1]=h[5]+1=5,就这样搜索并更新直到变成下图
6.这时再更新h[1]发现没有点可以用来更新h[1]了,于是此时h[1]=∞,使程序退出。
GAP 优化: 由于可行边定义为:(now,next)|h[now]=h[next]+1,所以若标号出现“断层”即有的标号对应的顶点个数为0,则说明剩余图中不存在增广路,此时便可以直接退出,降低了无效搜索。举个栗子:若结点标号为3的结点个数为0,而标号为4的结点和标号为2的结点都大于 0,那么在搜索至任意一个标号为4的结点时,便无法再继续往下搜索,说明图中就不存在增广路。此时我们可以以将h[1]=n 形式来变相地直接结束搜索
算法实现
1 |
|
算法复杂度
O(V^2 E)
预流推进算法
算法思想
预流推进算法是从一个预流出发对活跃顶点沿着允许弧进行流量增广,每次增广称为一次推进。在推进过程中,流一定满足流量限制条件,但一般不满足流量平衡条件, 因此只是一个伪流。此外, 如果一个伪流中, 从每个顶点(除源点 V s 、汇点 V t 外)流出的流量之和总是小于等于流入该顶点的流量之和, 称这样的伪流为预流。因此这类算法被称为预流推进算法。
算法流程
1 | 1.首先用一边 BFS 为图中每个顶点一个标号dis[v], 表示该点到v的最短路. |
算法实现
1 | const int size = 501; |
算法复杂度
如果该算法的Q是标准的FIFO队列,则时间复杂度为(n2m),最高标号不会超过n(超过时必无到汇的路径),所以n个点每个最多重新标号n次,两次标号之间m条边每条最多推流一次。如果是优先队列,并且标号最高的点优先的话,我们就得到了最高标号预流推进算法,其时间复杂度仅为n2m−−√.
最小费用最大流
简介
最小费用最大流是解决这么一种问题: 对于图中的每一条边来说, 除了有一个最大容量的属性以外,还有一个费用属性, 即流过这条边的单位流量的花费。求解的问题为在保证从源点到汇点的流量最大的前提下使得花费最少。
求解思想
我们来考虑这么一个问题: 在最短路的一些变形的题目中往往有这种题,每条路不仅仅有一个长度还有一个建设的费用, 最终求从起点到终点在保证路最短的前提下,使得花费的钱最少。当时我们是怎么求解的呢?
首先我们知道,最短路的长度是一定的,但是组成一条最短路的边是不一定的,所以我们在搜索这条最短路的时候只要通过调整待选边的优先级来控制搜索的方向就可以满足上述问题的要求。
这个问题跟我们现在求解的最小费用最大流问题神似啊,只要我们在寻找增广路的时候调整待选边的优先级来控制寻找方向,这个问题就可以解决了啊。我们直到对于一条增广路来说, 花费满足: cost=minFlow∗∑wi(i∈增广路上的边), 实际上这里的优先级就是每条边的长度认为是其单位流量的花费的最短路。
求解算法
基于最大流的三种算法,求解最小费用最大流也具有三种算法,我们来对比一下这三对算法:
1 | 最大流 EK 算法: 每次用广搜寻找一条最短的增广路(即包含最少的边),然后沿其增广。 |
算法实现
1 |
|